package cn.hy.controller;

import cn.hy.dto.UserDto;
import cn.hy.goods.RedisDecrementStock;
import cn.hy.interceptor.UserContext;
import cn.hy.limiter.IpUtil;
import cn.hy.limiter.RedisCountLimiter;
import cn.hy.mq.MqSecKillMessage;
import cn.hy.redis.key.GoodsKey;
import cn.hy.response.R;
import cn.hy.service.MqService;
import cn.hy.service.OrderService;
import cn.hy.service.SecKillService;
import cn.hy.vo.SecKillOrderVo;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

import static cn.hy.goods.GoodsWarmUp.localOverMap;


@RestController
public class SecKillController {

    @Resource
    private SecKillService secKillService;
    @Resource
    private MqService mqService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private OrderService orderService;

    @Resource
    private RedisCountLimiter redisCountLimiter;

    @Resource
    private RedisDecrementStock redisDecrementStock;

    @PostMapping(value = "/{path}/doSecKill")
    public R<Long> secKill(@PathVariable("path") String path,
                           @RequestParam("goodsId") Long goodsId) {
        UserDto user = UserContext.getUser();
        //验证 path
        if (!secKillService.checkPath(user.getNickname(), goodsId, path)) {
            return R.fail("访问太频繁!");
        }
        // 限流 ip - userid - method
        boolean reteLimiterForUserId = redisCountLimiter.tryAcquire("rate_limiter:" + user.getNickname(), 10, 1);
        if (!reteLimiterForUserId) {
            return R.fail("访问太频繁!");
        }
        boolean reteLimiterForIp = redisCountLimiter.tryAcquire("rate_limiter:" + IpUtil.getIP(), 10, 1);
        if (!reteLimiterForIp) {
            return R.fail("访问太频繁!");
        }
        boolean reteLimiterForMethod = redisCountLimiter.tryAcquire(this.getClass().getSimpleName() + "secKill", 3000, 1);
        if (!reteLimiterForMethod) {
            return R.fail("访问太频繁!");
        }
        //是否已经秒杀到
        SecKillOrderVo order = orderService.getSecKillOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return R.fail("不能重复秒杀");
        }
        //内存标记，减少redis访问
        if (localOverMap.get(goodsId)) {
            return R.fail("商品已经秒杀完毕！");
        }

        //redis 预减库存
        Long stock = stringRedisTemplate.opsForValue().decrement(GoodsKey.GOODS_STOCK.getKey(String.valueOf(goodsId)));
        if (stock == null || stock < 0) {
            localOverMap.put(goodsId, true);
            return R.fail("商品已经秒杀完毕！");
        }
//        但如果在高并发场景中，有多个请求同时扣减库存，大多数请求的 incrby 操作之后，结果都会小于0。 在优化如下
//        if (redisDecrementStock.execute(String.valueOf(goodsId))) {
//            localOverMap.put(goodsId, true);
//            return R.fail("商品已经秒杀完毕！");
//        }
        // 发送消息
        mqService.sendSecKillMsg(new MqSecKillMessage(user, goodsId));
        return R.success();
    }


    /**
     * orderId：成功
     * 1：秒杀失败
     * 0：排队中
     */
    @GetMapping(value = "/result")
    public R<Long> secKillResult(@RequestParam("goodsId") Long goodsId) {
        UserDto user = UserContext.getUser();
        return R.success(secKillService.getSecKillResult(Long.valueOf(user.getNickname()), goodsId));
    }


    /**
     * 验证码校验
     */
    @GetMapping(value = "/path")
    public R<String> getSecKillPath(@RequestParam("goodsId") long goodsId,
                                    @RequestParam(value = "verifyCode", defaultValue = "0") int verifyCode) {
        UserDto user = UserContext.getUser();
        Long userId = user.getId();
        String nickname = user.getNickname();
        boolean check = secKillService.checkVerifyCode(userId, goodsId, verifyCode);
        if (!check) {
            return R.fail("访问太频繁!");
        }
        String path = secKillService.createSecKillPath(nickname, goodsId);
        return R.success(path);
    }

}
